use std::{sync::{atomic::AtomicU64, Arc}, time::Instant};

use super::*;
use crate::{common::Chunk, index::FingerPrintStoreSelector, pipeline::MutExecutor};

use super::workflow::*;

pub struct ChunkDeduplicator {
    dedup_workflow:Option<Arc<DedupWorkFlow>>,
    fp_store:FingerPrintStoreSelector,
    dedup_time:u128,
}

impl ChunkDeduplicator {
    pub fn from_source(source:&SourcePool) -> Self {
        let dedup_workflow = source.dedup_workflow.clone();
        let fp_store = source.fp_store.clone();
        Self { 
            dedup_workflow, 
            fp_store,
            dedup_time:0,
        }
    }

    pub fn search_in_workflow(&mut self, chunk:&Chunk) -> Option<Arc<AtomicU64>> {
        let container_id = self.dedup_workflow
            .as_ref()
            .map(|f|f.find_and_insert(chunk))
            .flatten();
        return container_id;
    }
 
    pub fn search_chunk<F:FnMut(WriteItem)>(&mut self, chunk:SeqChunk, mut produce_write_item:F) -> BackupResult<()> {
        let start_time = Instant::now();
        let container_id = self.search_in_workflow(&chunk.chunk);
        if let Some(container_id) = container_id {
            // todo!();
            self.dedup_time += start_time.elapsed().as_micros();
            return Ok(());
        }
        let fp = chunk.chunk.fp();
        let container_id = self.fp_store.get_container_id_from_fp(fp).expect("index error");
        if let Some(container_id) = container_id {
            // todo!();
            self.dedup_time += start_time.elapsed().as_micros();
            return Ok(());
        }
        self.dedup_time += start_time.elapsed().as_micros();
        produce_write_item(WriteItem::Chunk(chunk));
        return Ok(());
    }

    pub fn get_dedup_time(&self) -> u128 {
        self.dedup_time
    }
}

impl Evaluated for ChunkDeduplicator {
    fn register(&self, evaluation:&Evaluation) {
        let mut eva = evaluation.lock();
        eva.register(BackupEvaluation::Time, "dedup time(us)");
    }

    fn set_value(&self, evaluation:&Evaluation) {
        let mut eva = evaluation.lock();
        eva.set("dedup time(us)", self.get_dedup_time());
    }
}

impl MutExecutor<WriteItem, WriteItem> for ChunkDeduplicator {
    type Error = BackupError;
    fn exec<'scope, F:FnMut(WriteItem)>(&'scope mut self, input:WriteItem, mut output:F) -> Result<(), Self::Error> {
        if let WriteItem::Chunk(seq_chunk) = input {
            self.search_chunk(seq_chunk, output)
        } else {
            output(input);
            Ok(())
        }
    }
}